#pragma once

#include <functional>
#include <memory>
#if defined(max)
#undef max
#include <google/protobuf/message.h>
#endif

#include "module_frame.hh"
#include "framework.hh"
#include "rpc_define.hh"
#include "../protocol/protocol_header.hh"
#include "../../protocol_files/common/generated_proto_header.hh"
#include "../../protocol_files/common/generated_message_id_defines.hh"
#include "../../corelib/object_pool.hpp"
#include "../../corelib/singleton.hpp"
#include "../../corelib/network/parameter.hh"

#include <google/protobuf/compiler/importer.h>
#include <google/protobuf/dynamic_message.h>

#include <list>
#include <unordered_map>

#ifdef GetMessage
#undef GetMessage
#endif

using namespace kratos::corelib;

// RPC(Remote Procedure Call) based on Google's Protobuf
class RPC {
    using CallMap = std::unordered_map<std::uint64_t, RPCCallBase*>;
    CallMap pendingCallMap_; // { Call's UUID, Pending RPC Call }
    std::unique_ptr<char> buffer_; // Send buffer
    RPCSkeletonBase* skeleton_; // Machine generated code of RPC skeleton
    static const uint32_t FLAT_BUFFER_SIZE = 1024; // Default buffer size of Flatbuffers' builder
    google::protobuf::compiler::Importer* importer_; // Protobuf .proto file importer
    google::protobuf::DynamicMessageFactory messageFactory_; // Protobuf message factory
    using MessageList = std::list<google::protobuf::Message*>;
    using MessagePool = std::unordered_map<std::string, MessageList>;
    MessagePool messagePool_;

public:
    // CTOR
    RPC();
    // DTOR
    ~RPC();
    // Registers skeleton of RPC
    // @param skeleton User implementating skeleton
    void registerSkeleton(RPCSkeletonBase* skeleton);
    // Sends a RPC call
    // @param pathID path ID
    // @param RPCID User defined RPC ID
    // @param msg The message needs to send
    // @param call RPC call
    // @param toCB The callback of call's timeout
    // @param timeout timeout in millionseconds
    // @retval true
    // @retval false fail
    bool RPCCallByPath(PathID pathID, std::uint64_t RPCID, const google::protobuf::Message& msg,
        RPCCallBase* call, RPCTimeoutCB toCB, std::time_t timeout);
    // Sends a RPC call
    // @param siteID site ID
    // @param RPCID User defined RPC ID
    // @param msg The message needs to send
    // @param call RPC call
    // @param toCB The callback of call's timeout
    // @param timeout timeout in millionseconds
    // @retval true
    // @retval false fail
    bool RPCCallBySite(const kratos::grid::SiteID& siteID, std::uint64_t RPCID,
        const google::protobuf::Message& msg, RPCCallBase* call, RPCTimeoutCB toCB, std::time_t timeout);
    // Sends a RPC call
    // @param entityID entity ID
    // @param RPCID User defined RPC ID
    // @param msg The message needs to send
    // @param call RPC call
    // @param toCB The callback of call's timeout
    // @param timeout timeout in millionseconds
    // @retval true
    // @retval false fail
    bool RPCCallByEntity(std::uint64_t entityID, std::uint64_t RPCID, const google::protobuf::Message& msg,
        RPCCallBase* call, RPCTimeoutCB toCB, std::time_t timeout);
    // Load .proto files from path
    // @param path .proto file path
    // @retval true
    // @retval false fail
    bool loadPBProto(const std::string& path);
    // New a protobuf message by name
    // @param typeName protobuf message type name
    // @retval nullptr message not found
    // @retval A generic protobuf message pointer
    google::protobuf::Message* newPBProtoMessage(const std::string& typeName);
    // Delete a protobuf message
    // @param msg protobuf message pointer
    void deletePBProtoMessage(google::protobuf::Message* msg);
    // Delete a protobuf message
    // @param msg protobuf message reference
    void deletePBProtoMessage(google::protobuf::Message& msg);

private:
    // Builds message for path or site RPC
    // @param msgID The message ID
    // @param RPCID RPC ID
    // @param msg Google protobuf message
    // @param call User defined RPC call
    // @param toCB User defiend timeout callback
    // @param timeout The timeout in millionseconds
    // @param [OUT] length The length of RPC message
    // @param siteID The site ID
    // @param [OUT] uuid The UUID of call
    // @retval true
    // @retval false
    bool buildMessage(std::uint32_t msgID, std::uint64_t RPCID, const google::protobuf::Message& msg,
        RPCCallBase* call, RPCTimeoutCB toCB, std::time_t timeout, std::size_t& length, std::uint64_t siteID,
            std::uint64_t& uuid);
    // Builds message for path or entity RPC
    // @param msgID The message ID
    // @param RPCID RPC ID
    // @param msg Google protobuf message
    // @param call User defined RPC call
    // @param toCB User defiend timeout callback
    // @param timeout The timeout in millionseconds
    // @param [OUT] length The length of RPC message
    // @param entityID The entity ID
    // @param [OUT] The path ID of locator
    // @param [OUT] uuid The UUID of call
    // @retval true
    // @retval false
    bool buildMessageEntity(std::uint32_t msgID, std::uint64_t RPCID, const google::protobuf::Message& msg,
        RPCCallBase* call, RPCTimeoutCB toCB, std::time_t timeout, std::size_t& length, std::uint64_t entityID, PathID& pathID,
            std::uint64_t& uuid);
    // To deal with the message from path
    // @param pathID The path ID
    // @param msgID The message ID
    // @param data message
    // @param size message length
    void onRPCPathMessage(PathID pathID, std::uint32_t msgID, const char* data, std::size_t size);
    // To deal with the message from site
    // @param siteID The site ID
    // @param msgID The message ID
    // @param data message
    // @param size message length
    void onRPCSiteMessage(const kratos::grid::SiteID& siteID, std::uint32_t msgID, const char* data,
        std::size_t size);
    // To deal with the message from entity
    // @param pathID The path ID
    // @param msgID The message ID
    // @param data message
    // @param size message length
    void onRPCEntityMessage(PathID pathID, std::uint32_t msgID, const char* data, std::size_t size);
    // To deal with the request from path
    // @param pathID The path ID
    // @param header The message header
    // @param data message
    // @param size message length
    void onPathReq(PathID pathID, const ProtocolRPCHeader& header, const char* data, std::size_t size);
    // To deal with the acknowledge from path
    // @param pathID The path ID
    // @param header The message header
    // @param data message
    // @param size message length
    void onPathAck(PathID pathID, const ProtocolRPCHeader& header, const char* data, std::size_t size);
    // To deal with the request from site
    // @param siteID The site ID
    // @param header The message header
    // @param data message
    // @param size message length
    void onSiteReq(const kratos::grid::SiteID& siteID, const ProtocolRPCHeader& header, const char* data,
        std::size_t size);
    // To deal with the acknowledge from site
    // @param siteID The site ID
    // @param header The message header
    // @param data message
    // @param size message length
    void onSiteAck(const kratos::grid::SiteID& siteID, const ProtocolRPCHeader& header, const char* data,
        std::size_t size);
    // To deal with the request from entity
    // @param pathID The path ID
    // @param header The message header
    // @param data message
    // @param size message length
    void onEntityReq(PathID pathID, const ProtocolRPCHeader& header, const char* data, std::size_t size);
    // To deal with the acknowledge from entity
    // @param pathID The path ID
    // @param header The message header
    // @param data message
    // @param size message length
    void onEntityAck(PathID pathID, const ProtocolRPCHeader& header, const char* data, std::size_t size);

    friend class ModuleFrame;
};

extern void sendPath(PathID pathID, flatbuffers::FlatBufferBuilder& builder, ProtocolRPCHeader & header,
    const google::protobuf::Message & msg);
extern void sendSite(const kratos::grid::SiteID & siteID, flatbuffers::FlatBufferBuilder& builder,
    ProtocolRPCHeader & header, const google::protobuf::Message & msg);
extern void sendPathAck(PathID pathID, std::uint64_t UUID, std::uint64_t RPCID, const google::protobuf::Message& msg);
extern void sendEntityAck(PathID pathID, PathID fromPathID, std::uint64_t UUID, std::uint64_t RPCID, const google::protobuf::Message& msg);
extern void sendSiteAck(const kratos::grid::SiteID & siteID, std::uint64_t UUID, std::uint64_t RPCID, const google::protobuf::Message& msg);

#define RPCRef (*kratos::corelib::Singleton<RPC>::instance())

#define NewEntityProxy(id, name)\
    RPCSkeleton::Entity::get##name(id)

#define NewPathProxy(id, name)\
    RPCSkeleton::Path::get##name(id)

#define NewSiteProxy(id, name)\
    RPCSkeleton::Site::get##name(id)
